﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace YYSoft.Tmsf
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        delegate void threadDelegate();
        public MainWindow()
        {
            InitializeComponent();

            txtUrl.Text = "http://www.tmsf.com/newhouse/property_330184_331788908_price.htm?isopen=&presellid=12483310&buildingid=&area=&allprice=&housestate=&housetype=&page=1";
        }

        // 开始抓取
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            txtTips.Text = string.Empty;
            var url = txtUrl.Text.Trim();
            if (url.IndexOf("http://www.tmsf.com") < 0)
            {
                MessageBox.Show("请输入透明售房网一房一价页面地址");
                return;
            }

            tbStart.IsEnabled = false;
            DoSpiderAsync(url);
        }


        private Task DoSpiderAsync(string url)
        {
            return Task.Factory.StartNew(() =>
            {
                try
                {
                    var page = 0;
                    var totalPage = 0;
                    var totalCount = 0;
                    var list = new List<HouseData>();
                    string GetSortKey(string houseUnit, string houseNo)
                    {
                        var zregex = $"(?<info>.+?)幢";
                        var zval = GetMatchValue(houseUnit, zregex, "info", "");
                        var dyregex = $"幢(?<info>.+?)单元";
                        var dyval = GetMatchValue(houseUnit, dyregex, "info", "");

                        var fh = houseNo.ReplaceByEmpty("室");
                        return $"{zval}-{dyval.PadLeft(2, '0')}-{fh.PadLeft(4, '0')}";
                    };
                    string GetHouseNo(string source)
                    {
                        var regex = $"<div>(?<info>.+?)</div>";
                        var val = GetMatchValue(source, regex, "info", "");
                        return val;
                    }
                    decimal GetNumberValue(string source)
                    {
                        if (source.IndexOf("<div>-</div>") > -1)
                            return -1;
                        var regex = ReplaceChar($"<span class=\"(?<info>.+?)\"></span>");
                        var ms = Regex.Matches(source, regex, RegexOptions.IgnoreCase);
                        var sb = new StringBuilder();
                        foreach (Match match in ms)
                        {
                            var val = match.Groups["info"].ToString();
                            switch (val)
                            {
                                case "numberone": sb.Append("1"); break;
                                case "numbertwo": sb.Append("2"); break;
                                case "numberthree": sb.Append("3"); break;
                                case "numberfour": sb.Append("4"); break;
                                case "numberfive": sb.Append("5"); break;
                                case "numbersix": sb.Append("6"); break;
                                case "numberseven": sb.Append("7"); break;
                                case "numbereight": sb.Append("8"); break;
                                case "numbernine": sb.Append("9"); break;
                                case "numberzero": sb.Append("0"); break;
                                case "numberdor": sb.Append("."); break;
                            }
                        }
                        return sb.ToString().ToDecimal();
                    }

                    do
                    {
                        var htmlCode = GetHtmlCode(url, 3);
                        if (string.IsNullOrEmpty(htmlCode))
                        {
                            MessageBox.Show("暂无内容");
                            break;
                        }
                        htmlCode = ReplaceChar(htmlCode);

                        #region 获取总页数
                        var totalPageInfoRegex = ReplaceChar($"<div class=\"spagenext\"><span>(?<info>.+?)</span>");
                        var totalPageInfo = GetMatchValue(htmlCode, totalPageInfoRegex, "info", "未找到总页数数据");
                        if (string.IsNullOrEmpty(totalPageInfo))
                            break;

                        var totalPageRegex = "页数(?<totalpage>.+?)总数";
                        var totalPageString = GetMatchValue(totalPageInfo, totalPageRegex, "totalpage", "无法提取总页数");
                        if (string.IsNullOrEmpty(totalPageString))
                            break;

                        page = totalPageString.Split('/')[0].ToInt32OrDefault();
                        totalPage = totalPageString.Split('/')[1].ToInt32OrDefault();

                        var totalCountRegex = "总数：(?<totalcount>.+?)套";
                        var totalCountString = GetMatchValue(totalPageInfo, totalCountRegex, "totalcount", "无法提取总套数");
                        if (string.IsNullOrEmpty(totalCountString))
                            break;
                        totalCount = totalCountString.ToInt32OrDefault();
                        #endregion

                        #region 获取表格中的数据
                        var tableRegex = ReplaceChar($"<table(.*?)class=\"sjtd\"(.*?)>(?<info>.+?)</table>");
                        var tableHtml = GetMatchValue(htmlCode, tableRegex, "info", "无法获取表格数据");
                        if (string.IsNullOrEmpty(tableHtml))
                            break;

                        var tableTrRegex = ReplaceChar($"<tr(.*?)>(?<info>.+?)</tr>");
                        var matches = Regex.Matches(tableHtml, tableTrRegex, RegexOptions.IgnoreCase);
                        foreach (Match match in matches)
                        {
                            var trHtml = match.Groups["info"].ToString();
                            var tableTdRegex = ReplaceChar($"<td(.*?)><a(.*?)>(?<info>.+?)</a></td>");

                            var tdMatches = Regex.Matches(trHtml, tableTdRegex, RegexOptions.IgnoreCase);

                            var houseUnit = tdMatches[0].Groups["info"].ToString();
                            var houseNo = GetHouseNo(tdMatches[1].Groups["info"].ToString());
                            var area = GetNumberValue(tdMatches[2].Groups["info"].ToString());
                            var innerArea = GetNumberValue(tdMatches[3].Groups["info"].ToString());
                            var rate = GetNumberValue(tdMatches[4].Groups["info"].ToString());

                            var roughPriceUnit = GetNumberValue(tdMatches[5].Groups["info"].ToString());
                            var fixturePriceUnit = GetNumberValue(tdMatches[6].Groups["info"].ToString());
                            var price = GetNumberValue(tdMatches[7].Groups["info"].ToString());

                            if (roughPriceUnit == -1)
                                continue;

                            list.Add(new HouseData()
                            {
                                SortKey = GetSortKey(houseUnit, houseNo),
                                HouseUnit = houseUnit,
                                HouseNo = houseNo,
                                Area = area.ToString(),
                                InnerArea = innerArea.ToString(),
                                UnitPrice = (roughPriceUnit + fixturePriceUnit < 0 ? "-" : (roughPriceUnit + fixturePriceUnit).ToString()),
                                Price = price < 0 ? "-" : price.ToString(),
                            });
                        }
                        #endregion

                        page++;

                        if (url.EndsWith("page="))
                            url = url + "1";
                        url = url.Replace($"page={page - 1}", $"page={page}");

                        CrossThreadExecuteUI(() =>
                        {
                            txtTips.Text = $"已获取 {page - 1} 页数据，当前数据量：{list.Count}\r\n" + txtTips.Text;
                        });

                        Thread.Sleep(100);

                    } while (page <= totalPage);

                    DoExportAsync(totalCount, list);
                }
                finally
                {
                    CrossThreadExecuteUI(() =>
                    {
                        tbStart.IsEnabled = true;
                    });
                }
            });
        }


        private Task DoExportAsync(int totalCount, List<HouseData> list)
        {
            return Task.Factory.StartNew(() =>
            {
                if ((list?.Count ?? 0) <= 0)
                    return;
                list = list.OrderBy(oo => oo.SortKey).ToList();
                var fileName = string.Format("数据{0}.xls", DateTime.Now.ToString("yyMMddHHmmss"));
                ToolHelper.CreateExcel($"共有{totalCount}套", list, "楼栋房号,楼栋,房号,面积,套内面积,单价,总价",
                    "SortKey,HouseUnit,HouseNo,Area,InnerArea,UnitPrice,Price",
                    fileName);

                CrossThreadExecuteUI(() =>
                {
                    txtTips.Text = $"数据导出成功，文件名称：{fileName}\r\n\r\n" + txtTips.Text;
                });
            });
        }

        private string GetHtmlCode(string url, int doTryCount = 2)
        {
            string action()
            {
                try
                {
                    var request = WebRequest.Create(url) as HttpWebRequest;
                    request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36";
                    request.Method = "get";
                    var response = request.GetResponse() as HttpWebResponse;
                    var streamReader = new StreamReader(response.GetResponseStream(), Encoding.GetEncoding("utf-8"));

                    string html = streamReader.ReadToEnd();
                    streamReader.Close();
                    response.Close();
                    request.Abort();
                    return html;
                }
                catch (Exception e1)//捕捉错误信息，采用WebClient方法获取
                {
                    try
                    {
                        var client = new WebClient();
                        var stream = client.OpenRead(url);
                        var readerOfStream = new StreamReader(stream, System.Text.Encoding.GetEncoding("utf-8"));
                        string aa = readerOfStream.ReadToEnd();
                        // Close the stream. 
                        stream.Close();
                        return aa;
                    }
                    catch (Exception e2)
                    {
                        //将两次报错的页面URL插入errorUrl_txt文件中
                        return null;
                    }
                }
            };

            var val = string.Empty;
            for (int i = 0; i < doTryCount; i++)
            {
                val = action();
                if (!string.IsNullOrEmpty(val))
                    break;
            }
            return val;

        }

        private string ReplaceChar(string str)
        {
            return str.Replace("\n", "")
                .Replace("\r", "")
                .Replace("&nbsp", "")
                .Replace("	", "")
                .Replace(" ", "");
        }

        private string GetMatchValue(string source, string regex, string key, string errorMsg)
        {
            var match = Regex.Match(source, regex, RegexOptions.IgnoreCase);
            if (!match.Success)
            {
                MessageBox.Show(errorMsg);
                return string.Empty;
            }
            var val = match.Groups[key].ToString();
            return val;
        }


        /// <summary>
        /// 跨线程更改UI
        /// </summary>
        /// <param name="action"></param>
        private void CrossThreadExecuteUI(Action action)
        {
            Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new threadDelegate(() =>
            {
                action();
            }));
        }
    }
}
